package com.wbf.demo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wbf.demo.security.*;
import com.wbf.demo.service.LoginService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static com.wbf.demo.common.CommonErrorMsgs.NO_LOGIN;
import static com.wbf.demo.common.CommonErrorMsgs.NO_FORBIDDEN;
/**
 * @program: security_demo
 * @description: security配置类
 * @author: guangdao
 * @create: 2022-04-14 17:35
 **/
@Configuration
@EnableWebSecurity //启用security
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启web层的controller启用注解机制的安全确认@PreAuthorize
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private LoginService loginService;

    /**
     * 通用过滤器
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/verificationCode/**", "/h2-console/**","/h2");
    }

    /**
     * security配置方法
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 定义登陆拦截器
                .addFilterAt(verifyUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                // 定义白名单
                .antMatchers("/login/username", "/logout", "/test")
                .permitAll().anyRequest().authenticated().and().csrf().disable().logout().clearAuthentication(true)
                // 登出请求，security自动删除token
                .logoutUrl("/logout").logoutSuccessUrl("/login")
                .and()
                // 请求处理过程中遇到认证异常时执行
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
                // 无访问权限异常时执行
                .accessDeniedHandler(accessDeniedHandler())
                .and()
                // session无效时处理策略
                .sessionManagement().invalidSessionStrategy(invalidSessionStrategy())
                // 管理session创建策略，IF_REQUIRED-需要时创建
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).sessionFixation().none()
                .and()
                // CORS配置
                .cors().configurationSource(corsConfigurationSource());
    }

    /**
     * 用户名密码拦截器
     * 请求->VerifyUsernamePasswordAuthenticationFilter.attemptAuthentication()->authenticationManager()->authSuccessHandler()
     * @return
     */
    @Bean
    public VerifyUsernamePasswordAuthenticationFilter verifyUsernamePasswordAuthenticationFilter() {
        VerifyUsernamePasswordAuthenticationFilter filter = new VerifyUsernamePasswordAuthenticationFilter();
        // 拦截通过后执行返回结果
        filter.setAuthenticationSuccessHandler(authSuccessHandler());
        // 拦截执行失败返回结果
        filter.setAuthenticationFailureHandler(authFailureHandler());
        // 请求地址-登陆接口地址
        filter.setFilterProcessesUrl("/login/username");
        // 业务处理-校验用户名密码、填充用户权限等操作
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    /**
     * 拦截器参数校验成功后业务处理器
     * @return
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManager() {
        DefaultAuthenticationProvider authenticationProvider = new DefaultAuthenticationProvider(loginService);
        List<AuthenticationProvider> providers = new LinkedList<>();
        providers.add(authenticationProvider);
        ProviderManager providerManager = new ProviderManager(providers);
        return providerManager;
    }

    /**
     * 执行成功返回结果
     * @return
     */
    @Bean
    public JsonAuthSuccessHandler authSuccessHandler() {
        JsonAuthSuccessHandler jsonAuthSuccessHandler = new JsonAuthSuccessHandler();
        return jsonAuthSuccessHandler;
    }

    /**
     * 执行失败返回结果
     * @return
     */
    @Bean
    public JsonAuthFailureHandler authFailureHandler() {
        JsonAuthFailureHandler jsonAuthFailureHandler = new JsonAuthFailureHandler(new ObjectMapper());
        return jsonAuthFailureHandler;
    }

    /**
     * 请求处理过程中遇到认证异常时执行
     * @return
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        JsonAuthenticationEntryPoint authenticationEntryPoint = new JsonAuthenticationEntryPoint(new ObjectMapper(), NO_LOGIN);
        return authenticationEntryPoint;
    }

    /**
     * 请求处理过程中遇到认证异常时执行
     * @return
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        AccessDeniedHandler accessDeniedHandler = new JsonAccessDeniedHandler(new ObjectMapper(), NO_FORBIDDEN);
        return accessDeniedHandler;
    }

    /**
     * session无效时处理策略
     * @return
     */
    @Bean
    public InvalidSessionStrategy invalidSessionStrategy() {
        return new JsonInvalidSessionStrategy(new ObjectMapper(), NO_LOGIN);
    }

    /**
     * CORS配置
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        // 设置允许访问的请求源http
        configuration.setAllowedOrigins(Arrays.asList("*"));
        // 设置允许访问的请求方法
        configuration.setAllowedMethods(Arrays.asList("*"));
        // 设置heard白名单，新增header需要添加白名单
        configuration.setAllowedHeaders(Arrays.asList("X-Auth-Token", "Origin", "X-Requested-With", "Content-Type",
                "Accept", "User-Token", "channel"));
        // 是否发送cookie信息
        configuration.setAllowCredentials(false);
        // 放行哪些头部信息(因为跨域访问默认不能获取全部头部信息)
        configuration.addExposedHeader("X-Auth-Token");
        configuration.addExposedHeader("token");
        configuration.addExposedHeader("Location");
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        // 添加映射路径
        corsConfigurationSource.registerCorsConfiguration("/**", configuration);
        return corsConfigurationSource;
    }
}
